package net.entelijan.cobean.orderlist;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

import javax.swing.AbstractListModel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.plaf.basic.BasicComboBoxRenderer;

import net.entelijan.cobean.core.ColumnDesc;
import net.entelijan.cobean.core.ICobean;
import net.entelijan.cobean.core.IInitializer;
import net.entelijan.cobean.core.SelectionMode;
import net.entelijan.cobean.core.impl.AbstractBoundPropertyValueComponentBean;
import net.entelijan.cobean.core.impl.INamedPropertyChangeListener;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

public class DefaultOrderlistInitializer<T> implements
		IInitializer<ICobean<List<T>>> {

	private List<ColumnDesc> columnDescs;

	private JList coList = null;

	private JButton coUpButton = null;

	private JButton coDownButton = null;

	private SelectionMode selectionMode = SelectionMode.MULTIPLE_INTERVAL;

	public DefaultOrderlistInitializer() {
		super();
	}

	@Override
	public ICobean<List<T>> initialize() {

		final AbstractBoundPropertyValueComponentBean<List<T>> re = new OrderListComponentBean<T>();
		re.setPropertyChangeListener(new OrderListPropertyChangeListener(re));
		ListCellRenderer renderer = new ListCellRenderer();

		coList.setCellRenderer(renderer);
		coList.setSelectionMode(this.selectionMode.getSwingSelectionMode());
		coUpButton.addActionListener(new UpButtonActionListener(re));

		coDownButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				if (re.getValue() != null) {
					int[] idxs = coList.getSelectedIndices();
					int min = getMinOfCompactBottomBlock(idxs, re.getValue()
							.size());
					for (int idx : idxs) {
						moveDown(re.getValue(), idx, min);
					}
					coList.clearSelection();
					coList.setSelectedIndices(moveDownIdices(idxs, re
							.getValue().size() - 1, min));
					coList.repaint();
				}
			}
		});

		return re;
	}

	protected int[] moveUpIdices(int[] idxs, int top) {
		HashSet<Integer> set = new HashSet<Integer>();
		for (int idx : idxs) {
			if (idx > top) {
				set.add(Math.max(idx - 1, 0));
			} else {
				set.add(idx);
			}
		}
		int[] re = new int[set.size()];
		int i = 0;
		for (Integer idx : set) {
			re[i++] = idx;
		}
		return re;
	}

	protected int[] moveDownIdices(int[] idxs, int maxIdx, int min) {
		HashSet<Integer> set = new HashSet<Integer>();
		for (int idx : idxs) {
			if (idx < min) {
				set.add(Math.min(idx + 1, maxIdx));
			} else {
				set.add(idx);
			}
		}
		int[] re = new int[set.size()];
		int i = 0;
		for (Integer idx : set) {
			re[i++] = idx;
		}
		return re;
	}

	private int getMaxOfCompactTopBlock(int[] idxs) {
		int re = 0;
		for (int i = 0; i < idxs.length; i++) {
			int idx = Arrays.binarySearch(idxs, i);
			if (idx < 0) {
				break;
			}
			re++;
		}
		return re;
	}

	private int getMinOfCompactBottomBlock(int[] idxs, int size) {
		int re = size - 1;
		for (int i = size - 1; i >= 0; i--) {
			int idx = Arrays.binarySearch(idxs, i);
			if (idx < 0) {
				break;
			}
			re--;
		}
		return re;
	}

	protected void moveDown(List<T> list, int idx, int min) {
		if (idx < min) {
			T elem = list.get(idx);
			list.remove(idx);
			list.add(idx + 1, elem);
		}

	}

	protected void moveUp(List<T> list, int idx, int top) {
		if (idx > top) {
			T elem = list.get(idx);
			list.remove(idx);
			list.add(idx - 1, elem);
		}
	}

	protected String toDisplayString(T value) {
		String re;
		if (columnDescs == null || columnDescs.isEmpty()) {
			re = "" + value;
		} else {
			StringBuilder sb = new StringBuilder();
			if (value != null) {
				BeanWrapper wvalue = new BeanWrapperImpl(value);
				int cnt = 0;
				for (ColumnDesc cd : columnDescs) {
					if (cnt++ > 0) {
						sb.append(" ");
					}
					Object val = wvalue.getPropertyValue(cd.getPropertyName());
					if (val != null) {
						if (cd.getConverter() != null) {
							val = cd.getConverter().objectToString(val);
						}
						sb.append(val);
					}
				}
			}
			re = sb.toString();
		}
		return re;
	}

	public List<ColumnDesc> getColumnDescs() {
		return columnDescs;
	}

	public void setColumnDescs(List<ColumnDesc> columnDescs) {
		this.columnDescs = columnDescs;
	}

	public JList getCoList() {
		return coList;
	}

	public void setCoList(JList coList) {
		this.coList = coList;
	}

	public JButton getCoUpButton() {
		return coUpButton;
	}

	public void setCoUpButton(JButton coUpButton) {
		this.coUpButton = coUpButton;
	}

	public JButton getCoDownButton() {
		return coDownButton;
	}

	public void setCoDownButton(JButton coDownButton) {
		this.coDownButton = coDownButton;
	}

	public SelectionMode getSelectionMode() {
		return selectionMode;
	}

	public void setSelectionMode(SelectionMode selectionMode) {
		this.selectionMode = selectionMode;
	}

	private final class UpButtonActionListener implements ActionListener {
		private final AbstractBoundPropertyValueComponentBean<List<T>> componentBean;

		private UpButtonActionListener(
				AbstractBoundPropertyValueComponentBean<List<T>> componentBean) {
			this.componentBean = componentBean;
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			int[] idxs = coList.getSelectedIndices();
			int top = getMaxOfCompactTopBlock(idxs);
			for (int idx : idxs) {
				moveUp(componentBean.getValue(), idx, top);
			}
			coList.clearSelection();
			coList.setSelectedIndices(moveUpIdices(idxs, top));
			coList.repaint();
		}
	}

	private class OrderListPropertyChangeListener implements
			INamedPropertyChangeListener {

		private final AbstractBoundPropertyValueComponentBean<List<T>> componentBeans;

		private OrderListPropertyChangeListener(
				AbstractBoundPropertyValueComponentBean<List<T>> componentBeans) {
			this.componentBeans = componentBeans;
		}

		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			coList.setModel(new AbstractListModel() {

				private static final long serialVersionUID = 1L;

				@Override
				public int getSize() {
					return componentBeans == null ? 0 : componentBeans
							.getValue().size();
				}

				@Override
				public Object getElementAt(int index) {
					return componentBeans.getValue().get(index);
				}
			});
		}

		@Override
		public String getName() {
			return "value";
		}
	}

	private class ListCellRenderer extends BasicComboBoxRenderer {

		private static final long serialVersionUID = 1L;

		@SuppressWarnings("unchecked")
		public Component getListCellRendererComponent(JList list, Object value,
				int index, boolean isSelected, boolean cellHasFocus) {
			JLabel re = (JLabel) super.getListCellRendererComponent(list,
					value, index, isSelected, hasFocus());
			re.setText(toDisplayString((T) value));
			return re;
		}
	}

	private static class OrderListComponentBean<T> extends
			AbstractBoundPropertyValueComponentBean<List<T>> {

		private List<T> value;

		@Override
		protected List<T> getValuePlain() {
			return value;
		}

		@Override
		protected void setValuePlain(List<T> value) {
			this.value = value;
		}
	}

}
